package mil.af.amc.adpetracker.util;

import java.util.LinkedHashMap;
import java.util.Map;


/**
 * <p>This class is a Builder to help generate the HTML required to create a
 * hyperlink. It allows you to create a link with text and an image, and to
 * chose wheather or not the image is actually displayed rather than the text
 * at in code.</p>
 *
 * <p>You may also chose to create the link, but display the link as normal
 * text, without the href. ie "<a href="http://www.google.com">google</a>"
 * becomes <samp>google</samp>.</p>
 *
 * <p>This link builder allows you to wrap the link with an html element, id
 * span or small, and allows you to append a bookmark, such as #top or
 * #naviation to it.</p>
 *
 * @author mnereson
 */
public class LinkBuilder {


    /**
     * <p>Requests a textual link.</p>
     *
     * @parameter int LINK_TEXT
     */
    public static final int LINK_TEXT = 0;

    /**
     * <p>Requests a gaphical link via the image that is set.</p>
     *
     * @parameter int LINK_IMAGE
     */
    public static final int LINK_IMAGE = 1;

    /**
     * <p></p>
     *
     * @parameter
     */
    private final String imageTag =
            "<img src=\"{source}\" alt=\"{alt}\" border=\"{border}\"/>";

    /**
     * <p></p>
     *
     * @parameter
     */
    private final String anchorTag =
            "<a href=\"{href}\" target=\"{target}\">{display}</a>";

    /**
     * <p></p>
     *
     * @parameter
     */
    private final String wrapperTag = "<{wrapper}>{output}</{wrapper}>";


    /**
     * <p>Name to display for the link. If an image is created as the link
     * then this value will be the alt on the image.</p>
     *
     * @parameter String name
     */
    private String name = "";


    /**
     * <p>Path to the image that this link may
     * be presented as. Absolute or relative</p>
     *
     * @parameter String imageUrl
     */
    private String imageUrl = "";


    /**
     * <p>Url the anchor tag will link to. Absoulte or relative.</p>
     *
     * @parameter String baseUrl
     */
    private String baseUrl = "";


    /**
     * <p>Target fame to open link if. Defalut is current window.</p>
     *
     * @parameter String target
     */
    private String target = "";


    /**
     * <p>Bookmark to send link to. This will append #bookmark and link
     * to an <tt>id</tt> field or <tt>name</tt> field in another HTML
     * element.</p>
     *
     * @parameter String bookmark
     */
    private String bookmark = "";

    /**
     * <p>Name of an HTML element to wrap the tag in. Leave "" for none.
     * Examples are <samp>small</samp> and <samp>div</samp>.</p>
     *
     * @parameter String wrapper
     */
    private String wrapper = "";

    /**
     * <p>Custom url parameters to append to the end of the parameters list.</p>
     *
     * @parameter String customParameters
     */
    private String customParameters = "";

    /**
     * <p>Map of properties and their values. LinkedHashMap map implemenation
     * maintains order so that the result is predictable and therefor
     * testable.</p>
     *
     * @parameter Map parameters
     */
    private final Map<Object, Object> parameters = new LinkedHashMap<Object, Object>();

    /* ----------------------------------------------------------constructors */


    /**
     * <p>Default constructor</p>
     */
    public LinkBuilder() {
        // do nothing
    }


    /**
     * <p>Typical constructor</p>
     *
     * @param name    String the text to show for this link or alt if an
     *                image is used
     * @param baseUrl String the url to navigate to
     */
    public LinkBuilder(final String name, final String baseUrl) {
        this.name = name;
        this.baseUrl = baseUrl;
    }

    /* -------------------------------------------------------------- setters */


    /**
     * <p>This base url should NOT contain any parameters. For example:
     * <samp>http://www.google.com</samp> or
     * <samp>mikenereson.blogspot.com</samp></p>
     *
     * @param baseUrl String the url to navigate to
     */
    public void setBaseUrl(final String baseUrl) {
        this.baseUrl = baseUrl;
    }


    /**
     * <p>Sets the target element of the anchor.
     * Allows for the link to be opened in a new window.</p>
     *
     * @param target String name of the target window
     */
    public void setTarget(final String target) {
        this.target = target;
    }


    /**
     * <p>Sets the name that will be displayed. If an image is included, this
     * name will be set as the alt of the image.</p>
     *
     * @param name String the text to show for this link
     */
    public void setName(String name) {

        if (name.equals(""))
            name = " ";

        this.name = name;
    }


    /**
     * <p>Appends a bookmark to the hyperlink. Bookmarks will set focus to
     * a specific element by using its id or name properties. For example:
     * <samp>navigation</samp> will output as <tt>#navigation</tt>.</p>
     *
     * @param bookmark String the name of the bookmark.
     */
    public void setBookmark(final String bookmark) {
        this.bookmark = bookmark;
    }


    /**
     * <p>Add a parameter to the url parameter list.</p>
     *
     * @param key   Object the name of the parameter
     * @param value Object the value of the parameter
     */
    public void addParameter(final Object key, final Object value) {
        parameters.put(key, value);
    }


    /**
     * <p>Appends custom parameters to the end of the parameter list. Useful
     * if another code library has paramters that it needs, such as the
     * <tt>Display Tag Library</tt>.</p>
     *
     * <p>For example: if {@link #baseUrl} is <samp>http://www.google.com</samp>
     * and there is already a {@link #parameters parameter}
     * <samp>"q","linkBuilder"</samp> the output of the
     * {@link #getBuiltUrl built url} is going to be
     * <samp>http://www.google.com?q=linkBuilder</samp>. From there
     * {@link #customParameters} are just appended to the end. So in this case,
     * if customParameters are <samp>&uid=test&one=one</samp> the generated url
     * is <samp>http://www.google.com?q=linkBuilder&uid=test&one=one</samp.</p>
     *
     * <p><tt>&</tt> or <tt>?</tt>, as first characters are not important.</p>
     *
     * @param customParameters String
     */
    public void setCustomParameters(final String customParameters) {

        if (customParameters != null && !customParameters.equals("")) {

            final String fistChar = customParameters.substring(0, 1);

            if (fistChar.equals("?") || fistChar.equals("&")) {

                this.customParameters = customParameters.substring(1);

            } else {

                this.customParameters = customParameters;
            }
        }
    }


    /**
     * <p>Sets the relative or absolute path to the image to be used
     * in the link.</p>
     *
     * @param imageUrl Stirng path to the image
     */
    public void setImageUrl(final String imageUrl) {
        this.imageUrl = imageUrl;
    }


    /**
     * <p>Sets the name of the wrapper tag to wrap the link in if
     * so desired.</p>
     *
     * <p>For example: if {@link #wrapper} is set to <samp>small</samp> the
     * {@link #getLink(int,boolean)} would return
     * <samp>&lt;small>&lt;a href..>&lt;/a>&lt;/small></samp></p>
     *
     * @param wrapper String name of the tag to wrap the link in
     */
    public void setWrapper(final String wrapper) {
        this.wrapper = wrapper;
    }

    /* ----------------------------------------------------------------- gets */


    /**
     * <p>Gets the link that is generated from setting this builder's propertis.
     * The link can be in a few different formats:
     * <code>LinkBuilder.LINK_IMAGE</code> returns the image that is set.
     * <code>LinkBuilder.LINK_TEXT</code> returns the name that is set.
     * Passing <tt>true</tt> gives you an active link, that is to say, it is an
     * achor tag that is returned. Passing <tt>false</tt> gives you the image
     * or name without the anchor tag around it. This is useful if you need to
     * build a link and determine at runtime weather or not the user should be
     * able to get to that link. </p>
     *
     * @param linkType int <code>LinkBuilder.LINK_IMAGE</code> or
     *                 <code>LinkBuilder.LINK_TEXT</code>
     * @param active   boolean <tt>true</tt> to get an href, <tt>false</tt> to
     *                 get just the name or image tag.
     * @return String a complete anchor tag or just the name
     * @throws IllegalArgumentException if linkType is not valid
     * @throws IllegalStateException    if the {@link #name} and {@link #baseUrl}
     *                                  properties are not both set
     */
    public String getLink(final int linkType, final boolean active) {

        assertValidLinkType(linkType);
        assertNotEmpty("name", name);

        /* display will either be the name or the image tag */
        final String display = linkType ==
                LinkBuilder.LINK_TEXT ? name : getImageTag();

        /* if not active just return the name or link + the wrapper */
        if (!active) {

            if (wrapper.equals("")) {

                return display;

            } else {

                return wrapperTag.replace("{wrapper}", wrapper)
                        .replace("{output}", display);
            }
        }


        final String url = getBuiltUrl();

        String anchor = anchorTag.replace("{href}", url)
                .replace("{target}", target)
                .replace("{display}", display);

        /* remove anchor element if not used */
        if (anchor.contains("target=\"\""))
            anchor = anchor.replace(" target=\"\"", "");

        if (wrapper.equals("")) {

            return anchor;

        } else {

            return wrapperTag.replace("{wrapper}", wrapper)
                    .replace("{output}", anchor);
        }
    }


    /**
     * <p>Get the url that is constructued from the {@link #baseUrl},
     * {@link #parameters}, {@link #customParameters}, and
     * {@link #bookmark} properties.</p>
     *
     * @return String a url
     * @throws IllegalStateException when {@link #baseUrl} is empty or
     *                               <tt>null</tt>
     */
    public String getBuiltUrl() {

        /* assertNotEmpty proper state */
        assertNotEmpty("baseUrl", baseUrl);

        StringBuffer url = new StringBuffer(baseUrl);

        url.append("?");

        for (Map.Entry<Object, Object> entry : parameters.entrySet()) {

            url.append(entry.getKey().toString());
            url.append("=");
            url.append(entry.getValue().toString());
            url.append("&");
        }

        if (!customParameters.equals(""))
            url.append(customParameters);

        if (url.substring(url.length() - 1).equals("&")
                || url.substring(url.length() - 1).equals("?"))
            url = new StringBuffer(url.substring(0, url.length() - 1));

        if (!bookmark.equals(""))
            url.append("#").append(bookmark);

        return url.toString();
    }

    /* -------------------------------------------------------------- private */


    /**
     * <p>Validates that a String property has been initialized.</p>
     *
     * @param name  String name of the property being checked
     * @param value String value of the property being checked
     * @throws IllegalStateException when the value is empty or
     *                               <tt>null</tt>.
     */
    private void assertNotEmpty(final String name, final String value) {

        if (value == null || value.equals(""))
            throw new IllegalStateException(name + "attribute must " +
                    "be set for each usage of the LinkBuilder");
    }


    /**
     * <p>Validates that all neccessary components are in place</p>
     *
     * @param linkType int <code>LinkBuilder.LINK_IMAGE</code>  or
     *                 <code>LinkBuilder.LINK_TEXT</code>
     * @throws IllegalArgumentException if linkType is not valid.
     */
    private void assertValidLinkType(final int linkType) {

        if (linkType != LinkBuilder.LINK_IMAGE
                && linkType != LinkBuilder.LINK_TEXT)
            throw new IllegalArgumentException("linkType attribute must be " +
                    "LinkBuilder.LINK_IMAGE or LinkBuilder.LINK_TEXT");

        if (linkType == LinkBuilder.LINK_IMAGE)
            assertNotEmpty("imageUrl", imageUrl);
    }


    /**
     * <p>Generates the HTML to represent the image</p>
     *
     * @return String a complete image tag
     */
    private String getImageTag() {

        return imageTag.replace("{source}", imageUrl)
                .replace("{alt}", name)
                .replace("{border}", "0");
    }
}